/*
 * Copyright 2011-2012, David George, Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.magneato.infra;

import java.io.Serializable;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.acls.domain.AuditLogger;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;

/**
 * A simple implementation of the Acl interface.
 * 
 * @see org.springframework.security.acls.model.Acl
 * @author David George
 * @date	29/Apr/2011
 */
public class SimpleAclImpl implements MutableAcl {
	private static final long serialVersionUID = 1L;
	private ObjectIdentity oi;
	private List<AccessControlEntry> aces;
	private transient AuditLogger auditLogger = new ConsoleAuditLogger();
	private final Log _logger = LogFactory.getLog(InMemoryAclServiceImpl.class);

	public SimpleAclImpl(ObjectIdentity oi, List<AccessControlEntry> aces) {
		this.oi = oi;
		this.aces = aces;
	}

	public List<AccessControlEntry> getEntries() {
		return aces;
	}

	public ObjectIdentity getObjectIdentity() {
		return oi;
	}

	public Sid getOwner() {
		return null; // owner concept is optional, we don't use it
	}

	public Acl getParentAcl() {
		return null; // we don't use inheritance
	}

	public boolean isEntriesInheriting() {
		return false; // we don't use inheritance
	}

	/**
	 * This is the actual authorization logic method, and is used whenever
	 * ACL authorization decisions are required.
	 * 
	 * An array of Sids are presented, representing security identifies of the
	 * current principal. In addition, an array of Permissions is presented
	 * which will have one or more bits set in order to indicate the permissions
	 * needed for an affirmative authorization decision. An array is presented
	 * because holding any of the Permissions inside the array will be
	 * sufficient for an affirmative authorization.
	 * 
	 * The actual approach used to make authorization decisions is left to the
	 * implementation and is not specified by this interface. For example, an
	 * implementation MAY search the current ACL in the order the ACL entries
	 * have been stored. If a single entry is found that has the same active
	 * bits as are shown in a passed Permission, that entry's grant or deny
	 * state may determine the authorization decision. If the case of a deny
	 * state, the deny decision will only be relevant if all other Permissions
	 * passed in the array have also been unsuccessfully searched. If no entry
	 * is found that match the bits in the current ACL, provided that
	 * isEntriesInheriting() is true, the authorization decision may be passed
	 * to the parent ACL. If there is no matching entry, the implementation MAY
	 * throw an exception, or make a predefined authorization decision.
	 * 
	 * This method must operate correctly even if the Acl only represents a
	 * subset of Sids, although the implementation is permitted to throw one of
	 * the signature-defined exceptions if the method is called requesting an
	 * authorization decision for a Sid that was never loaded in this Acl.
	 * 
	 * Parameters: @param permission - the permission or permissions required
	 * (at least one entry required)
	 * 
	 * @param sids
	 *            - the security identities held by the principal (at least one
	 *            entry required)
	 * @param administrativeMode
	 *            - if true denotes the query is for administrative purposes and
	 *            no logging or auditing (if supported by the implementation)
	 *            should be undertaken Returns: true if authorization is granted
	 * 
	 * 
	 * @return
	 * @throws NotFoundException
	 */
	public boolean isGranted(List<Permission> permissions, List<Sid> sids,
			boolean administrativeMode) throws NotFoundException {

		AccessControlEntry firstRejection = null;

		/**
		 * Loop over all supplied permissions
		 */
		for (Permission permission : permissions) {
			/*
			 * For each permission loop over security identifiers
			 */
			for (Sid sid : sids) {
				// Attempt to find exact match for this permission mask and SID
				boolean scanNextSid = true;

				for (AccessControlEntry ace : aces) {

					_logger.info(permission + " " + ace.getPermission());

					if (((ace.getPermission().getMask() & permission.getMask()) != 0)) {
						_logger.info(sid + " getSid: " + ace.getSid());
						_logger.info("id " + ace.getId());

						if (ace.getSid().equals(sid)
								|| ace.getId().equals("aceOther")) {
							/*
							 * Found a matching ACE, so its authorization
							 * decision will prevail
							 */
							if (ace.isGranting()) {
								// Success
								if (!administrativeMode) {
									auditLogger.logIfNeeded(true, ace);
								}

								return true;
							} else {
								/*
								 * Failure for this permission, so stop search
								 * We will see if they have a different
								 * permission (this permission is 100% rejected
								 * for this SID)
								 */
								if (firstRejection == null) {
									// Store first rejection for auditing
									// reasons
									firstRejection = ace;
								}

								scanNextSid = false; // helps break the loop

								break; // exit "aces" loop
							}
						}
					}
				}// for:ace

				if (!scanNextSid) {
					break; // exit SID for loop (now try next permission)
				}
			}// for:sid
		}// for:permission

		if (firstRejection != null) {
			/*
			 * We found an ACE to reject the request at this point, as no other
			 * ACEs were found that granted a different permission
			 */
			if (!administrativeMode) {
				auditLogger.logIfNeeded(false, firstRejection);
			}

			return false;
		}

		throw new NotFoundException(
				"Unable to locate a matching ACE for passed permissions and SIDs");
	}

	public boolean isSidLoaded(List<Sid> arg0) {
		/*
		 * we use in-memory structure, not external DB, so all entries are (if I
		 * correctly understand meaning of this method)
		 */
		return true;
	}

	public void deleteAce(int arg0) throws NotFoundException {
		// TODO Auto-generated method stub

	}

	public Serializable getId() {
		return oi.getIdentifier();
	}

	public void insertAce(int arg0, Permission arg1, Sid arg2, boolean arg3)
			throws NotFoundException {
		// TODO Auto-generated method stub

	}

	public void setEntriesInheriting(boolean arg0) {
		// TODO Auto-generated method stub

	}

	public void setOwner(Sid arg0) {
		// TODO Auto-generated method stub

	}

	public void setParent(Acl arg0) {
		// TODO Auto-generated method stub

	}

	public void updateAce(int arg0, Permission arg1) throws NotFoundException {
		// TODO Auto-generated method stub

	}

}